/*
 * Copyright (c) 2010-2011, Yawning <yawninglol at gmail.com>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 *   * Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *
 *   * Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 *
 *   * Neither the name of the Mew Developers nor the
 *     names of its contributors may be used to endorse or promote products
 *     derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
 * THE POSSIBILITY OF SUCH DAMAGE.
 */

package yawning.mew.cat;

import yawning.mew.BuffsDebuffs;
import yawning.mew.BuffsDebuffs.Buff;
import yawning.mew.BuffsDebuffs.Debuff;
import yawning.mew.MewProperty;
import yawning.mew.PropertiesBase;
import yawning.mew.character.CharacterModel;
import yawning.mew.character.Clazz;
import yawning.mew.character.Glyph;
import yawning.mew.character.Talent;
import yawning.mew.character.TalentTree;
import yawning.mew.character.Toon;

/**
 * Model Backend Infrastructure
 *
 * @author Yawning <yawninglol at gmail.com>
 */
public class CatModel extends CharacterModel {
	private static final MewProperty BLOODINTHEWATERTIME = new MewProperty("BloodInTheWaterTime", 0.0);	// Amount of time Blood in the Water is active
	private static final MewProperty BUG_GLYPH_SHRED_SIMCRAFT = new MewProperty("Bug.GlyphOfShredSimCraft", false);	// Glyph of Shred bug model (SimulationCraft consistency).
	private static final MewProperty BUG_GLYPH_SHRED_3SEC = new MewProperty("Bug.GlyphOfShred3Sec", false);	// Glyph of Shred bug model (Alt hypothesis).

	// Common backend internals.
	protected CatStatLogic levelDep;		// Level dependent parameters.
	protected Toon druid;					// Character stats.
	protected BuffsDebuffs buffsDebuffs;	// Buffs/Debuffs.

	// Backend support.
	protected PropertiesBase config;		// Backend Properties.

	// Druid stats.
	protected int baseEnergyCap = 100;
	protected int maxShiftEnergy = 0;

	protected double pBonusBiteCrit;

	protected double pPrimalFury;
	protected double pFurySwipes;
	protected double pBloodInTheWater;
	protected double pPredatorsSwiftness;
	protected double oocPPM;
	protected double pOoCProc;

	// Multipliers
	protected double multCrit;
	protected double multSpellCrit;
	protected double multRoar;
	protected double multPhysVuln;
	protected double multSpellVuln;
	protected double multDamageMult;
	protected double multMangle = 1.3;
	protected double multShredRnT;
//	protected double multBleedMastery;
	protected double multBleedDarkIntent;
	protected double multBiteFeralAggression;
	protected double multMangleGlyph;
	protected double multRipGlyph;
	protected double multFurySwipes = 3.10;
	protected double multSwipe = 5.25;
	protected double multTigersFury = 1.15;
	protected double multTargetBaseArmor;

	protected int nrFeralFFStacksPerCast;

	// Costs.
	protected int clawCost = 40;
	protected int mangleCost = 35;
	protected int rakeCost = 35;
	protected int shredCost = 40;
	protected int ripCost = 30;
	protected int srCost = 25;
	protected int biteCost = 25;
	protected int maimCost = 35;
	protected int pounceCost = 50;
	protected int ravageCost = 60;
	protected int ravageCostStampede;
	protected int swipeCost = 45;
	protected int feralChargeCost = 10;
	protected int skullBashCost = 25;

	// Durations.
	protected int oocBuffDuration = 15;
	protected int feralFFDebuffDuration = 60 * 5;
	protected int feralFFCDDuration = 6;
	protected int berserkBuffDuration;
	protected int berserkCDDuration = 180;
	protected int tigersFuryBuffDuration = 6;
	protected int tigersFuryCDDuration;
	protected int mangleDebuffDuration = 60;
	protected int feralChargeCDDuration;
	protected int skullBashCDDuration;
	protected int basePounceDebuffDuration;
	protected int baseRakeDebuffDuration;
	protected int baseRipDebuffDuration;
	protected int roarInitialDuration;
	protected int roarCpBonus = 5;
	protected int maimCDDuration = 10;
	protected int furySwipesCDDuration = 3;
	protected int ripGlyphExtDuration;
	protected int ripGlyphExtMaxTicks;
	protected int predatorsSwiftnessDuration = 8;
	protected double basePounceTickInterval = 3.0;
	protected double baseRakeTickInterval = 3.0;
	protected double baseRipTickInterval = 2.0;
	protected double stampedeBuffDuration;
	protected int maimStunDuration = 1; 	// Per CP.
	protected double pounceStunDuration;

	// Other talents.
	protected int primalMadnessEnergyGain;
	protected int kotjEnergyGain;

	// Energy regeneration.
//	protected double energyRegenerationRate;
//	protected double energyTickInterval;

	// Set bonuses.
	protected double tier_10_4pc_rakeMult;
	protected double tier_11_2pc_rakeMult;
	protected double tier_11_4pc_apMult;
	protected double tier_11_4pc_buffStacks;
	protected double tier_11_4pc_buffDuration = 30;
	protected double tier_12_2pc_damageMult = 0.1;
	protected double tier_12_2pc_dotDuration = 4;
	protected double tier_12_2pc_dotTickInterval = 2;
	protected double tier_12_4pc_procRate = 0.2;
	protected int tier_12_4pc_extDuration = 2;

	// Blizzard side server oddities.
	protected boolean bug_glyphOfShred_SimCraft;		// Glyph of Shred bug, SimCraft workaround.
	protected boolean bug_glyphOfShred_3Sec;			// Glyph of Shred bug, alternative workaround.
	protected boolean bug_pooledProcEffects;			// Munching/Rolling/Refresh modeling.

	CatModel(Toon t, PropertiesBase config) {
		super(t);
		this.druid = t;
		this.buffsDebuffs = new BuffsDebuffs(config.getProperties());
		this.levelDep = new CatStatLogic(mStatLogic);
		this.config = config;
		calculateBaseStats();
	}

	/**
	 * Get Blood in the Water duration.
	 */
	protected double getBloodInTheWaterTime() {
		double bitwTime = config.getDouble(BLOODINTHEWATERTIME);

		// Tier 13 - "your Blood in the Water talent now causes Ferocious Bite to refresh the duration of your Rip on targets with 60% or less health."
		if (mToon.tier_13_2pc == true) {
			bitwTime = Math.min(1.0, bitwTime * 2.4);	// The default is 25%, so just extend it by 2.4x.  (Note: this assumes the rate of mob hp decrease is linear.)
		}

		return bitwTime;
	}

	@Override
	public void addHasteRating(double d) {
		super.addHasteRating(d);
		mEnergyTickCacheValid = false;
	}

	@Override
	public void multiplyHaste(double m) {
		super.multiplyHaste(m);
		mEnergyTickCacheValid = false;
	}

	// Cats get AP from Strength and Agility
    @Override
	public double getAttackPower() {
		double attackPower = ((getAgility() - 10) * 2 + (getStrength() - 10) + mToon.attackPower + bonusAttackPower) * apMult;
		if (tier_11_4pc_buffStacks > 0) attackPower *= (1 + tier_11_4pc_buffStacks * tier_11_4pc_apMult);
		return attackPower;
	}

	// Cats have weapon speed forced to 1.0
    // Note: Can't just be done with this by super.getWeaponDamage() / mToon.getWeaponSpeed * 1000 because that breaks bonusWeaponDamage
	// BUG: Fury Swipes does not benefit from Bonus Weapon Damage (4.0.6 PTR)
    @Override
	public double getWeaponDamage() {
		return getWeaponDamage(false);
	}

	public double getWeaponDamage(boolean ignoreBonus) {
		double weaponDmg = 1 + (double)(mToon.weaponTop + mToon.weaponBottom) / 2  + getAttackPower() * mToon.weaponSpeed / 14000;
		weaponDmg = weaponDmg / mToon.weaponSpeed * 1000;
		if (ignoreBonus == false) weaponDmg += bonusWeaponDamage;

		return weaponDmg;
	}

	@Override
	public double getBaseWeaponSpeed() {
		return 1.0;
	}

	public double getShiftGCD() {
		// XXX: Is this affected by haste at all?
		return 1.5;
	}

	private static final TalentTree FERAL_COMBAT = TalentTree.get(Clazz.DRUID, "Feral Combat");
	static final Talent FUROR = Talent.getByName(Clazz.DRUID, "Furor");
	static final Talent HEARTOFTHEWILD = Talent.getByName(Clazz.DRUID, "Heart of the Wild");
	static final Talent MASTERSHAPESHIFTER = Talent.getByName(Clazz.DRUID, "Master Shapeshifter");
	static final Talent RENDANDTEAR = Talent.getByName(Clazz.DRUID, "Rend and Tear");
	static final Talent FERALAGGRESSION = Talent.getByName(Clazz.DRUID, "Feral Aggression");
	static final Talent ENDLESSCARNAGE = Talent.getByName(Clazz.DRUID, "Endless Carnage");
	static final Talent FERALCHARGE = Talent.getByName(Clazz.DRUID, "Feral Charge");
	static final Talent STAMPEDE = Talent.getByName(Clazz.DRUID, "Stampede");
	static final Talent BRUTALIMPACT = Talent.getByName(Clazz.DRUID, "Brutal Impact");
	static final Talent PRIMALMADNESS = Talent.getByName(Clazz.DRUID, "Primal Madness");
	static final Talent KINGOFTHEJUNGLE = Talent.getByName(Clazz.DRUID, "King of the Jungle");
	static final Talent PRIMALFURY = Talent.getByName(Clazz.DRUID, "Primal Fury");
	static final Talent FURYSWIPES = Talent.getByName(Clazz.DRUID, "Fury Swipes");
	static final Talent BLOODINTHEWATER = Talent.getByName(Clazz.DRUID, "Blood in the Water");
	static final Talent BERSERK = Talent.getByName(Clazz.DRUID, "Berserk");
	static final Talent PREDATORYSTRIKES = Talent.getByName(Clazz.DRUID, "Predatory Strikes");

	static final Glyph GLYPH_SAVAGEROAR = Glyph.getByName("Savage Roar");
	static final Glyph GLYPH_RIP = Glyph.getByName("Rip");
	static final Glyph GLYPH_MANGLE = Glyph.getByName("Mangle");
	static final Glyph GLYPH_BERSERK = Glyph.getByName("Berserk");
	static final Glyph GLYPH_BLOODLETTING = Glyph.getByName("Bloodletting");
	static final Glyph GLYPH_TIGERSFURY = Glyph.getByName("Tiger's Fury");
	static final Glyph GLYPH_FEROCIOUSBITE = Glyph.getByName("Ferocious Bite");
	static final Glyph GLYPH_FERALCHARGE = Glyph.getByName("Feral Charge");

	/**
	 * Calculates the base stats for a given Druid.
	 */
	private void calculateBaseStats() {
		// Agi, Str, AP.
		double statMult = buffsDebuffs.isBuff(Buff.STATMULT) ? 1.05 : 1;
		multiplyAgility(statMult * (druid.isArmorSpec ? 1.05: 1));
		multiplyStrength(statMult);
		multiplyIntellect(statMult);

		int buffAgiStr = buffsDebuffs.isBuff(Buff.AGISTR) ? mStatLogic.getBuffAgiStr(): 0;
		addAgility(buffAgiStr);
		addStrength(buffAgiStr);

        // Cat form AP:
        //  * 2 AP per Agi or Str, first 10 in each are not counted.
        //  * Talents/Buffs apply multiplicatively after.
		multiplyAttackPower(druid.mTalentSpec == FERAL_COMBAT ? 1.25 : 1);
		double hotwMult = druid.getTalent(HEARTOFTHEWILD) * 0.03;
		hotwMult += (druid.getTalent(HEARTOFTHEWILD) > 1) ? 0.01 : 0.0;
		multiplyAttackPower(1.0 + hotwMult);
		multiplyAttackPower(buffsDebuffs.isBuff(Buff.APMULT) ? 1.2 : 1);

		// Crit
		addMeleeCrit(mStatLogic.getBasePhysicalCrit() + ((druid.getTalent(MASTERSHAPESHIFTER) > 0) ? 0.04 : 0));
		addMeleeCrit(buffsDebuffs.isBuff(Buff.CRIT) ? 0.05 : 0);
		addSpellCrit(mStatLogic.getBaseSpellCrit());
		addSpellCrit(buffsDebuffs.isBuff(Buff.CRIT) ? 0.05 : 0);
		addSpellCrit(buffsDebuffs.isDebuff(Debuff.SPELLCRIT) ? 0.05 : 0);
		pBonusBiteCrit = druid.getTalent(RENDANDTEAR) * 0.08;
		pBonusBiteCrit += (druid.getTalent(RENDANDTEAR) > 1) ? 0.01 : 0;

		// Haste
		multiplyHaste((buffsDebuffs.isBuff(Buff.DARKINTENT)) ? 1.03 : 1);
		multiplyMeleeHaste(buffsDebuffs.isBuff(Buff.MELEEHASTE) ? 1.1 : 1);
		multiplySpellHaste(buffsDebuffs.isBuff(Buff.SPELLHASTE) ? 1.05 : 1);

		// Multipliers
		multCrit = physicalCritMult;
		multSpellCrit = spellCritMult;
		multRoar = 1.8 + ((druid.hasGlyph(GLYPH_SAVAGEROAR)) ? 0.05 : 0);
		multPhysVuln = ((buffsDebuffs.isDebuff(Debuff.PHYSVULN)) ? 1.04 : 1);
		multSpellVuln = ((buffsDebuffs.isDebuff(Debuff.SPELLVULN)) ? 1.08 : 1);
		multDamageMult = ((buffsDebuffs.isBuff(Buff.DAMAGEMULT)) ? 1.03 : 1);
		double bonusRnT = druid.getTalent(RENDANDTEAR) * 0.07;
		bonusRnT -= (druid.getTalent(RENDANDTEAR) > 1) ? 0.01 : 0;
		multShredRnT = ((druid.getTalent(RENDANDTEAR) > 0) ? 1 + bonusRnT : 1);
		multBleedDarkIntent = ((buffsDebuffs.isBuff(Buff.DARKINTENT)) ? 1.03 : 1);
		multBiteFeralAggression = 1 + druid.getTalent(FERALAGGRESSION) * 0.05;
		multMangleGlyph = (druid.hasGlyph(GLYPH_MANGLE)) ? 1.1 : 1;
		multRipGlyph = druid.hasGlyph(GLYPH_RIP) ? 1.15 : 1;

		// Durations
		berserkBuffDuration = 15 + (druid.hasGlyph(GLYPH_BERSERK) ? 10 : 0);
		pounceStunDuration = 3 + (druid.getTalent(BRUTALIMPACT) * 0.5);
		basePounceDebuffDuration = 18;
		baseRakeDebuffDuration = 9 + (druid.getTalent(ENDLESSCARNAGE) * 3);
		baseRipDebuffDuration = 16;
		roarInitialDuration = 9 + (druid.getTalent(ENDLESSCARNAGE) * 4);
		ripGlyphExtDuration = (druid.hasGlyph(GLYPH_BLOODLETTING) ? 2 : 0);
		ripGlyphExtMaxTicks = 3;	// XXX: This is actually, 3, 4, 5 with buggy Blizzard coding.
		stampedeBuffDuration = (druid.getTalent(STAMPEDE) > 0) ? 8 : 0;
		tigersFuryCDDuration = 30 - (druid.hasGlyph(GLYPH_TIGERSFURY) ? 3 : 0);
		skullBashCDDuration = 60 - (druid.getTalent(BRUTALIMPACT) * 25);
		feralChargeCDDuration = 30 - (druid.hasGlyph(GLYPH_FERALCHARGE) ? 2 : 0);

		// Energy regen talents.
		primalMadnessEnergyGain = druid.getTalent(PRIMALMADNESS) * 10;
		kotjEnergyGain = druid.getTalent(KINGOFTHEJUNGLE) * 20;
		maxShiftEnergy = (druid.getTalent(FUROR) == 3) ? 100 : druid.getTalent(FUROR) * 33;

		pPrimalFury = druid.getTalent(PRIMALFURY) * 0.5;
		pFurySwipes = druid.getTalent(FURYSWIPES) * 0.05;
		pBloodInTheWater = druid.getTalent(BLOODINTHEWATER) * 0.5;
		pPredatorsSwiftness = druid.getTalent(PREDATORYSTRIKES) * 0.1;
		oocPPM = 3.5;
		pOoCProc = oocPPM * (1.0/60);

		nrFeralFFStacksPerCast = 1 + druid.getTalent(FERALAGGRESSION);

		// Varaible costs
		ravageCostStampede = ravageCost - (ravageCost / 2 * druid.getTalent(STAMPEDE));

		// Set/Equipment bonuses.
		//
		// PvP:
		// Gloves - Reduces the energy and rage cost of your Skull Bash by 10.
		//
		// Tier 10:
		// Druid T10 Feral 2P Bonus - Your Swipe (Bear) and Lacerate abilities deal 20% additional damage and the cost of your Rip ability is reduced by 10 energy.
		// Druid T10 Feral 4P Bonus - Your Enrage ability no longer decreases your armor and instead decreases all damage taken by 12%, and increases the damage done by your Rake ability by 10%.
		//
		// Tier 11:
		// Druid T11 Feral 2P Bonus - Increases the periodic damage done by your Rake and Lacerate abilities by 10%.
		// Druid T11 Feral 4P Bonus - Each time you use Mangle (Cat) you gain a 1% increase to attack power for 30 sec stacking up to 3 times, and the duration of your Survival Instinct ability is increased by 50%.
		//
		// Tier 12:
		// Druid T12 Feral 2P Bonus - Your attacks with Mangle, Maul, and Shred deal 10% additional damage as Fire damage over 4 sec.
		// Druid T12 Feral 4P Bonus - Your finishing moves have a 20% chance per combo point to extend the duration of Berserk by 2 + (20 * ComboPoints) sec and when your Barkskin ability expires you gain an additional 10% chance to dodge for 12 sec.
		//
		// Tier 13:
		// Druid T13 Feral 2P Bonus - Pulverize now also grants your Mangle (Bear) critical strikes a 100% chance to trigger Savage Defense, and your Blood in the Water talent now causes Ferocious Bite to refresh the duration of your Rip on targets with 60% or less health.
		// Druid T13 Feral 4P Bonus - Frenzied Regeneration also affects all raid and party members. This effect cannot be triggered if you have been in Bear Form for less than 15 sec. In addition, using Tiger's Fury will cause your next Ravage to cost no energy, not require stealth, and have no positioning requirement for 10 sec.
		skullBashCost -= druid.pvp_gloves ? 10 : 0;
		ripCost -= druid.tier_10_2pc ? 10 : 0;
		tier_10_4pc_rakeMult = druid.tier_10_4pc ? 1.1 : 1.0;
		tier_11_2pc_rakeMult = druid.tier_11_2pc ? 1.1 : 1.0;
		tier_11_4pc_apMult = druid.tier_11_4pc ? 0.01 : 0.0;
		tier_11_4pc_buffStacks = 0;

		// Blizzard bugs
		bug_glyphOfShred_SimCraft = config.getBool(BUG_GLYPH_SHRED_SIMCRAFT);
		bug_glyphOfShred_3Sec = config.getBool(BUG_GLYPH_SHRED_3SEC);
		bug_pooledProcEffects = config.getBool(BUG_POOLED_PROC_EFFECTS);

		if (bug_glyphOfShred_3Sec) ripGlyphExtDuration = (ripGlyphExtDuration > 0) ? 3 : 0;
	}

	public final double getBleedMastery() {
		return 1 + getMastery() * 0.03125;
	}

	// Energy regeneration scales with haste.
	//
	// The formula as far as I can tell is thus:
	// 100 / ((BaseTimeToEnergyCap) / (1 + haste%/ 100)) e/sec
	public final double getEnergyRegenRate() {
		return 10 * getHaste();
	}

	private double mEnergyTickCache;
	private boolean mEnergyTickCacheValid;
	public final double getEnergyTickInterval() {
		if (!mEnergyTickCacheValid) {
			mEnergyTickCache = 0.1 / getHaste();
			mEnergyTickCacheValid = true;
		}
		return mEnergyTickCache;
	}

	public final int getPounceDebuffDuration() {
		return basePounceDebuffDuration;
	}

	public final double getPounceTickInterval() {
		return basePounceTickInterval;
	}

	public final int getRakeDebuffDuration() {
		return baseRakeDebuffDuration;
	}

	public final double getRakeTickInterval() {
		return baseRakeTickInterval;
	}

	public final int getRipDebuffDuration() {
		return baseRipDebuffDuration;
	}

	public final double getRipTickInterval() {
		return baseRipTickInterval;
	}

	@Override
	public String toString() {
		return config.valuesToString(new MewProperty[] {BLOODINTHEWATERTIME, BUG_GLYPH_SHRED_SIMCRAFT} );
	}
}
